package com.arpa.wms.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.ntocc.common.common.cache.PartyCache;
import com.arpa.ntocc.common.common.constant.StrConsts;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.common.util.OrderIdGeneratorUtil;
import com.arpa.wms.domain.consts.CommonConst;
import com.arpa.wms.domain.consts.NumConst;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.dto.PrepackagedDTO;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.*;
import com.arpa.wms.domain.vo.PickingItemWithRegisterVO;
import com.arpa.wms.domain.vo.PickingVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.NUM_ONE;
import static com.arpa.wms.domain.consts.NumConst.NUM_ZERO;

/**
 * 拣货确认service
 *
 * @author liuxr
 * @date 2021年05月19日 9:19
 */
@Service
@Log4j2
public class PickingConfirmationService {


    @Resource
    public PartyCache partyCache;
    @Resource
    public RedissonClient redissonClient;
    @Autowired
    private IPickingService iPickingService;
    @Autowired
    private IPickingRegisterService iPickingRegisterService;
    @Autowired
    private IPickingItemService iPickingItemService;
    @Autowired
    private IOutboundService outboundService;
    @Autowired
    private IOutboundItemService iOutboundItemService;
    @Autowired
    private IOutboundRegisterService  iOutboundRegisterService;
    @Autowired
    private OutCheckService outCheckService;
    @Autowired
    private IDeliveryDistributionService iDeliveryDistributionService;
    @Autowired
    private IDeliveryItemService iDeliveryItemService;
    @Autowired
    private IDeliveryService iDeliveryService;
    @Autowired
    private IOutboundService iOutboundService;
    @Autowired
    private ILocationService iLocationService;
    @Autowired
    private IInventoryService inventoryService;
    @Autowired
    private InventoryOperationService inventoryOperationService;
    @Resource
    private IInventoryRecordService iInventoryRecordService;
    @Resource
    private IInventoryService iInventoryService;
    @Resource
    private IWmsTaskService iWmsTaskService;
    @Resource
    private IPrepackagedItemService prepackagedItemService;
    @Resource
    private IPrepackagedDistributionService prepackagedDistributionService;
    @Resource
    private IPrepackagedService prepackagedService;



    /**
     * 按单拣货
     * @author liuxr
     * @date 2021/5/27 19:33
     * @param vo
     */
    public void singleCheck(PickingVO vo){
        String groupCode = UserUtil.getBranchCode();
        // 判断拣货单及状态
        Picking picking = iPickingService.getOne(new QueryWrapper<Picking>().lambda()
                .eq(Picking::getCode, vo.getCode())
                .eq(Picking::getGroupCode,groupCode));
        if (picking == null) {
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // “待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (StrUtil.equals(picking.getStatus(), PickingStatusEnum.FINISH_PICK.getValue())) {
            throw new ServiceException("拣货单已经拣货完成，请确认。");
        }
        //校验按单拣货打包数量
        validatesingleCheckPackageNum(picking.getCode(),vo.getPickingItemWithRegisterVOList(),groupCode);
        //拣货确认明细
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = vo.getPickingItemWithRegisterVOList();
        // 获取拣货明细及明细对应的实际拣货数量
        Map<PickingItem, BigDecimal> itemWithRealQuantity = calcRealPickingQuantity(vo);
        // 更新明细，如果存在缺货登记的信息， 则需要1. 更新各明细的缺货数量；2.返还分配的数据，并锁定
        //处理缺货
        singlePickinghandleOutStock(vo.getCode(),itemWithRealQuantity,groupCode);
        //按单拣货，更新拣货明细拣货数量、缺货数量及拣货状态拣货完成
        singlePickingUpdateItemPickingQuantityAndStockoutQuantity(picking.getCode(),itemWithRealQuantity,groupCode);

        savePCUnSaveRegister(pickingItemWithRegisterVOS,picking.getCode(),picking.getWavePickingCode(),groupCode);

        // 删除确认单中未拣货的明细数据
        iPickingRegisterService.remove(new QueryWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,groupCode).eq(PickingRegister::getPickingCode, picking.getCode())
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));

        //  更新分配表拣货数量、 更新分配表明细 拣货数量
        updateDeliveryInfo(picking.getDeliveryCode(),picking.getWavePickingCode(),groupCode);

        //处理订单完全缺货
        handleWholeStockOutDelivery(picking.getWavePickingCode(),groupCode);

        picking = iPickingService.getOne(new LambdaQueryWrapper<Picking>()
                .eq(Picking::getGroupCode,groupCode)
                .eq(Picking::getCode, vo.getCode()));
        log.info("拣货确认：【step2】，生成拣货单");
        Outbound outbound = this.pickConfirmationGenOutboundInfo(picking);
        if(null ==outbound){
            return;
        }
        log.info("拣货确认：【step4】，判断是否跳过复核");
        if (picking.getReview() == 0) {
            RLock ol = null;
            try {
                ol = redissonClient.getLock(getOutConfirmLockKey(outbound.getCode()));
                if (ol.tryLock()) {
                    if (OrderTagEnum.QUANTITY.getValue().equals(outbound.getOrderTag())) {
                        //TODO 按单拣货需要重新梳理
                        log.info("按单拣货出库");
                        iOutboundService.outCheck(outbound);
                    }else {
                        //log.info("拣货确认，出库单复核：【step1】，处理作废单");
                        //outCheckService.handleInvalid(outbound);
                        log.info("拣货确认，出库单复核：【step2】，调用奇门确认接口");
                        Set<String> successCode = outCheckService.deliveryConfirmation(outbound);
                        if(IterUtil.isEmpty(successCode)){
                            throw new ServiceException("出库确认失败，订单数据有误，请联系客服人员");
                        }
                        log.info("拣货确认，出库单复核：【step3】，更新库存");
                        outCheckService.updateInventory(successCode, outbound.getCode(),groupCode);
                        log.info("拣货确认，出库单复核：【step4】，更新订单信息");
                        outCheckService.updateDeliveryInfo(successCode,groupCode);
                        log.info("拣货确认，出库单复核：【step5】，处理出库单状态");
                        outCheckService.outConfirm(outbound.getCode(), outbound.getWavePickingCode(),groupCode);
                        log.info("拣货确认，出库单复核：【step6】，异步释放容器");
                        outCheckService.releaseContainer(outbound.getWavePickingCode(),groupCode);
                    }
                } else {
                    log.error("出库单【{}】，出库确认获得锁失败", outbound.getCode());
                }
            }  catch (Exception e) {
                log.error(StrConsts.CATCH_MESSAGE, e);

                throw new ServiceException(e.getMessage());
            } finally {
                try {
                    if (null != ol && ol.isLocked()) {
                        ol.unlock();
                    }
                } catch (Exception e) {
                    log.error("出库确认释放锁失败！！", e);
                }
            }
        }
    }

    private void validatesingleCheckPackageNum(String pickingCode,List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS,String groupCode) {
        Picking picking = iPickingService.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getCode, pickingCode)
                .eq(Picking::getGroupCode,groupCode));
        if (picking != null) {
            //不是预打包分配，不进行打包校验
            String prepackagedCode = picking.getPrepackagedCode();
            if (StrUtil.isEmpty(prepackagedCode)) {
                return;
            } else {

                List<PickingItem> pickingItemList = iPickingItemService.list(new LambdaQueryWrapper<PickingItem>()
                        .eq(PickingItem::getGroupCode,groupCode).eq(PickingItem::getPickingCode, pickingCode)
                        .eq(PickingItem::getStatus,PickingStatusEnum.FINISH_PICK.getValue()));
                Map<String,BigDecimal> integerMap = prepackagedItemService.list(new LambdaQueryWrapper<PrepackagedItem>()
                        .eq(PrepackagedItem::getPrepackagedCode, prepackagedCode).eq(PrepackagedItem::getGroupCode, groupCode)).stream()
                        .collect(Collectors.toMap(PrepackagedItem::getGoodCode,PrepackagedItem::getCombinationQuantity));
                Set<Integer> packQuantitySet = new HashSet<>();
                if (IterUtil.isEmpty(pickingItemList)) {
                    //1、商品未拣货，判断各商品填写的拣货数量/单件数量是否一致，如果不一致，提示：商品分配数量与实际包裹数不一致，不能生成拣货单；
                    pickingItemWithRegisterVOS.stream().forEach(info -> {
                        BigDecimal packQuantity = info.getPickingQuantity().
                                divide(MapUtil.get(integerMap, info.getGoodCode(), BigDecimal.class));
                        Boolean bigDecimalIsInteger = judgeBigDecimalIsInteger(packQuantity);
                        if (!bigDecimalIsInteger) {
                            throw new ServiceException("包裹数量不是整数，请重新拣货");
                        } else {
                            packQuantitySet.add(packQuantity.intValue());
                        }
                    });
                    if (packQuantitySet.size() > 1) {
                        throw new ServiceException("商品分配数量与实际包裹数不一致，不能拣货确认");
                    }
                } else {
                    //商品已拣货,不存在部分商品拣货的情况，要么都拣货，要么都不拣货
                    return;
                }

            }
        }
    }

    private boolean judgeBigDecimalIsInteger(BigDecimal decimal) {
        BigDecimal decimal1 = decimal.setScale(6, BigDecimal.ROUND_HALF_UP);
        BigDecimal integer = decimal.setScale(0, BigDecimal.ROUND_HALF_UP);
        if (decimal1.compareTo(integer) == 0) {
            return true;
        } else {
            return false;
        }
    }
    /**
     * 订单的拣货数量为0，说明完全缺货
     *
     * @param wavePickingCode
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleWholeStockOutDelivery(String wavePickingCode,String groupCode) {
        Delivery delivery = iDeliveryService.getOne(new QueryWrapper<Delivery>().lambda().eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode,groupCode));
        if (delivery.getPickNum().compareTo(BigDecimal.ZERO) == NUM_ZERO) {
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda().eq(Delivery::getGroupCode,groupCode).eq(Delivery::getCode, delivery.getCode()).set(Delivery::getPickNum, BigDecimal.ZERO).set(Delivery::getWavePickingCode, StrUtil.EMPTY).set(Delivery::getPickStockOutNum,BigDecimal.ZERO).set(Delivery::getStatus,DeliveryStatusEnum.UN_DISTRIBUTION.getValue()).set(Delivery::getRecheckQuantity,BigDecimal.ZERO).set(Delivery::getRecheckRefundQuantity,BigDecimal.ZERO).set(Delivery::getDistributionNum,BigDecimal.ZERO));
            iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda().eq(DeliveryItem::getGroupCode,groupCode).eq(DeliveryItem::getDeliveryCode, delivery.getCode()).set(DeliveryItem::getPickingQuantity, BigDecimal.ZERO).set(DeliveryItem::getPickStockOutNum, BigDecimal.ZERO).set(DeliveryItem::getWavePickingCode,StrUtil.EMPTY).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO).set(DeliveryItem::getStatus,DeliveryStatusEnum.ADD.getValue()).set(DeliveryItem::getRecheckQuantity,BigDecimal.ZERO).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO));
            iDeliveryDistributionService.remove(new QueryWrapper<DeliveryDistribution>().lambda().eq(DeliveryDistribution::getGroupCode,groupCode).eq(DeliveryDistribution::getDeliveryCode, delivery.getCode()));
        }
    }



    /**
     * 更新发货模块各表的拣货数量
     *
     * @param deliveryCode
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDeliveryInfo(String deliveryCode,String wavePickingCode,String groupCode) {
        // 更新分配单拣货数量
        iDeliveryDistributionService.updatePickingQuantityForCheck(deliveryCode,groupCode);
        // 更新订单明细中的拣货数量
        iDeliveryItemService.updatePickingQuantityForCheck(deliveryCode,groupCode);
        // 更新订单中的总拣货数量
        iDeliveryService.updatePickNumByDistributionData(deliveryCode);

        // 更新发货单状态
        if (StrUtil.isEmpty(wavePickingCode)) {
            iDeliveryService.updateDeliveryStatusIfEmptyDistribution(deliveryCode,groupCode);
        } else {
            //波次号wavePickingCode不为空时，更新发货单的状态为待复核
            iDeliveryService.updateStatusByWavePickingCode(wavePickingCode,DeliveryStatusEnum.UN_RECHECK.getValue(),groupCode);
            iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda().eq(DeliveryItem::getWavePickingCode, wavePickingCode)
                    .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode())
                    .set(DeliveryItem::getStatus, DeliveryStatusEnum.UN_RECHECK.getValue()));
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public void savePCUnSaveRegister(List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS,String pickingCode,String wavePickingCode,String groupCode) {
        WmsTask task = iWmsTaskService.getOne(new QueryWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode)
                .eq(WmsTask::getGroupCode,groupCode));

        List<PickingRegister> unSaveRegisters = new ArrayList<>();
        // PC前端只可维护 未拣货状态 的数据，保存时也只保存拣货数量大于0的数据
        List<PickingItemWithRegisterVO> unSaveItemWithRegisters = pickingItemWithRegisterVOS.stream()
                .filter(e -> {
                    boolean flag1 = PickingRegisterStatusEnum.NOT_PICK.getValue().equals(e.getStatus());
                    BigDecimal pickingQuantity = ObjectUtil.defaultIfNull(e.getPickingQuantity(), BigDecimal.ZERO);
                    boolean flag2 = pickingQuantity.compareTo(BigDecimal.ZERO) > 0;
                    return flag1 && flag2;
                }).collect(Collectors.toList());

        if (!unSaveItemWithRegisters.isEmpty()) {
            String createName = partyCache.translate(UserUtil.getCode());
            unSaveItemWithRegisters.forEach(e -> {
                PickingRegister pickingRegister = new PickingRegister();
                BeanUtil.copyProperties(e, pickingRegister);
                pickingRegister.setStatus(PickingRegisterStatusEnum.FINISH_PICKING.getValue());
                pickingRegister.setCode(IdUtil.simpleUUID());
                pickingRegister.setCreatedBy(UserUtil.getCode());
                pickingRegister.setCreatedName(createName);
                pickingRegister.setModifiedBy(StrUtil.EMPTY);
                pickingRegister.setModifiedName(StrUtil.EMPTY);
                pickingRegister.setWavePickingCode(wavePickingCode);
                pickingRegister.setGroupCode(groupCode);
                if (task != null) {
                    pickingRegister.setTaskCode(task.getCode());
                    pickingRegister.setOperatorBy(task.getOperatorBy());
                    pickingRegister.setOperatorName(task.getOperatorName());
                }
                unSaveRegisters.add(pickingRegister);
            });
            iPickingRegisterService.saveBatch(unSaveRegisters);
        }
    }

    /**
     * 按单拣货处理缺货
     * @author liuxr
     * @date 2021/5/27 20:02
     * @param itemRealQuantity
     * @return com.arpa.core.web.Result
     */
    @Transactional(rollbackFor = Exception.class)
    public void singlePickinghandleOutStock(String pickingCode ,Map<PickingItem, BigDecimal> itemRealQuantity,String groupCode) {
        // 遍历，如果明细分配数量大于实际拣货量则缺货；如果小于则说明拣多了
        PickingItem item;
        BigDecimal pickQuantity;
        List<InventoryOperationDto> subtract =new ArrayList<>();
        //所有临时库存、原始库存code
        Set<String> inventoryCode = new HashSet<>();
        Set<String> oldInventoryCode = new HashSet<>();
        //缺货信息
        itemRealQuantity.forEach((k,v)->{
            // 获取差异数量 = 分配数量 - 缺货登记数量 - （实际+新增拣货数量）
            BigDecimal diffQuantity = k.getDistributionQuantity().subtract(k.getStockoutQuantity()).subtract(v);
            if (diffQuantity.compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("拣货量与缺货数量总和超过了实际分配量，请确认！");
            } else if (diffQuantity.compareTo(BigDecimal.ZERO) > 0) {
                // 缺货登记返还库存
                InventoryOperationDto dto = new InventoryOperationDto();
                dto.setUuid(IdUtil.simpleUUID());
                dto.setQuantity(diffQuantity);
                dto.setInventoryCode(k.getInventoryCode());
                subtract.add(dto);
                inventoryCode.add(k.getInventoryCode());
                inventoryCode.add(k.getOldInventoryCode());
                oldInventoryCode.add(k.getOldInventoryCode());
            }
        });
        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        List<Inventory> reInventorys = new ArrayList<>();
        if(IterUtil.isNotEmpty(subtract)){
            //根据缺货登记信息获取临时库存标识
            //Set<String> tmpInventoryCodes = subtract.stream().map(InventoryOperationDto::getInventoryCode).collect(Collectors.toSet());
            if(IterUtil.isEmpty(inventoryCode)){
                throw  new ServiceException("没有找到临时库存信息");
            }
            //查询库存信息
            List<Inventory> inventorieList = inventoryService.list(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode,inventoryCode)
                    .eq(Inventory::getGroupCode,groupCode));
            List<Inventory> oldInventorieList = inventoryService.list(new LambdaQueryWrapper<Inventory>()
                    .eq(Inventory::getGroupCode,groupCode)
                    .in(Inventory::getOldInventoryCode,oldInventoryCode)
                    .eq(Inventory::getIsLock,1)
                    .eq(Inventory::getTempInventory,0)
            );
            if(IterUtil.isEmpty(inventorieList)){
                throw  new ServiceException("没有找到库存信息");
            }
            Map<String,Inventory> inventoryMap = inventorieList.stream().collect(Collectors.toMap(Inventory::getCode,r->r));
            //所有库存库位
            Set<String> oldLocationCodes = inventorieList.stream()
                    .map(Inventory::getOldLocation)
                    .filter(StrUtil::isNotBlank).collect(Collectors.toSet());
            if(IterUtil.isEmpty(oldLocationCodes)){
                throw  new ServiceException("没有找到退还原库位信息");
            }
            // 获取退减临时库位
            List<Location> oldLocationList = iLocationService.list(new LambdaQueryWrapper<Location>().select(Location::getCode,Location::getSpaceCode)
                    .eq(Location::getGroupCode,groupCode)
                    .in(Location::getCode, oldLocationCodes));
            if(IterUtil.isEmpty(oldLocationList)){
                throw  new ServiceException("没有找到退还原库位信息");
            }

            Map<String,Location> oldLocationMap = oldLocationList.stream().collect(Collectors.toMap(Location::getCode,r->r));

            //需要处理缺货
            subtract.forEach(r -> {
                Inventory inv = inventoryMap.get(r.getInventoryCode());
                if(null==inv){
                    throw  new ServiceException("没有找到临时库存信息");
                }
                Location oldLocation = oldLocationMap.get(inv.getOldLocation());
                if(null==oldLocation){
                    throw  new ServiceException("没有找到退还原库位信息");
                }
                r.setInventory(inv);
                //临时库存减库存操作记录
                InventoryRecord record = new InventoryRecord();
                BeanUtil.copyProperties(inv, record);
                record.setCode(IdUtil.simpleUUID());
                record.setGroupCode(groupCode);
                record.setInventoryCode(r.getInventoryCode());
                record.setSourceCode(pickingCode);
                record.setNum(r.getQuantity().negate());
                record.setType(InventoryRecordTypeEnum.PICK.getValue());
                inventoryRecords.add(record);

                Inventory inventory = new Inventory();
                BeanUtil.copyProperties(inv,inventory);
                inventory.setCode(IdUtil.fastSimpleUUID());
                inventory.setCreatedBy(null);
                inventory.setQuantity(r.getQuantity());
                inventory.setUseQuantity(BigDecimal.ZERO);
                inventory.setOldInventoryCode(inv.getOldInventoryCode());
                inventory.setTempCode(StrUtil.EMPTY);
                inventory.setTempItemCode(StrUtil.EMPTY);
                inventory.setTempRegisterCode(StrUtil.EMPTY);
                // 抹去容器号等信息
                inventory.setContainerBarCode(StrUtil.EMPTY);
                inventory.setIsLock(1);
                inventory.setTempInventory(0);
                inventory.setLocation(oldLocation.getCode());
                inventory.setWarehouseSpaceCode(oldLocation.getSpaceCode());
                reInventorys.add(inventory);
            });

            //要新增的库存
            List<Inventory> insertList = new ArrayList<>();
            //要更新的库存
            List<InventoryOperationDto> updateList = new ArrayList<>();

            if(IterUtil.isNotEmpty(reInventorys)){
                if(IterUtil.isEmpty(oldInventorieList)){
                    for (Inventory reInventory : reInventorys) {
                        InventoryRecord reRecord = new InventoryRecord();
                        BeanUtil.copyProperties(reInventory, reRecord);
                        reRecord.setCode(IdUtil.simpleUUID());
                        reRecord.setGroupCode(groupCode);
                        reRecord.setInventoryCode(reInventory.getCode());
                        reRecord.setSourceCode(pickingCode);
                        reRecord.setNum(reInventory.getQuantity());
                        reRecord.setType(InventoryRecordTypeEnum.PICK.getValue());
                        inventoryRecords.add(reRecord);
                    }
                    insertList.addAll(reInventorys);
                }else{
                    Map<String,Inventory> oldInventoryMap = oldInventorieList.stream().collect(Collectors.toMap(Inventory::getOldInventoryCode,r->r));
                    for (Inventory reInventory : reInventorys) {
                        Inventory oldInventory = oldInventoryMap.get(reInventory.getOldInventoryCode());
                        if(null==oldInventory){
                            InventoryRecord reRecord = new InventoryRecord();
                            BeanUtil.copyProperties(reInventory, reRecord);
                            reRecord.setCode(IdUtil.simpleUUID());
                            reRecord.setGroupCode(groupCode);
                            reRecord.setInventoryCode(reInventory.getCode());
                            reRecord.setSourceCode(pickingCode);
                            reRecord.setNum(reInventory.getQuantity());
                            reRecord.setType(InventoryRecordTypeEnum.PICK.getValue());
                            inventoryRecords.add(reRecord);
                            insertList.add(reInventory);
                        }else{
                            InventoryRecord reRecord = new InventoryRecord();
                            BeanUtil.copyProperties(reInventory, reRecord);
                            reRecord.setCode(IdUtil.simpleUUID());
                            reRecord.setGroupCode(groupCode);
                            reRecord.setInventoryCode(oldInventory.getCode());
                            reRecord.setSourceCode(pickingCode);
                            reRecord.setNum(reInventory.getQuantity());
                            reRecord.setType(InventoryRecordTypeEnum.PICK.getValue());
                            inventoryRecords.add(reRecord);
                            InventoryOperationDto dto = new InventoryOperationDto();
                            dto.setInventoryCode(oldInventory.getCode());
                            dto.setQuantity(reInventory.getQuantity());
                            updateList.add(dto);
                        }
                    }
                }
            }
            //减少临时库存
            inventoryOperationService.subtractBatch(subtract);
            //批量插入退减库存
            if(IterUtil.isNotEmpty(insertList)) {
                iInventoryService.saveBatch(insertList);
            }
            //更新原库存
            if(IterUtil.isNotEmpty(updateList)) {
                inventoryOperationService.addBatch(updateList);
            }
            //保存库存记录
            iInventoryRecordService.saveBatch(inventoryRecords);
        }
    }

    /**
     * 按单拣货，更新拣货明细拣货数量、缺货数量及拣货状态拣货完成
     * @author liuxr
     * @date 2021/5/27 20:41
     * @param itemRealQuantity
     */
    @Transactional(rollbackFor = Exception.class)
    public void singlePickingUpdateItemPickingQuantityAndStockoutQuantity(String pickingCode ,Map<PickingItem, BigDecimal> itemRealQuantity,String groupCode){
        List<PickingItem> temp = new ArrayList<>();
        itemRealQuantity.forEach ((k,v)->{
            // 获取差异数量 = 分配数量 - 缺货登记数量 - （实际+新增拣货数量）
            BigDecimal diffQuantity = k.getDistributionQuantity().subtract(k.getStockoutQuantity()).subtract(v);
            if (diffQuantity.compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("拣货量与缺货数量总和超过了实际分配量，请确认！");
            }
            k.setPickingQuantity(v);
            temp.add(k);
        });
        // 更新缺货数量(分配数量-实际拣货数量）、更新拣货数量、状态
        iPickingItemService.singlePickingUpdateItemPickingQuantityAndStockoutQuantity(temp);
        // 总拣货数量
        BigDecimal totalPickQuantity = BigDecimal.ZERO;
        for (BigDecimal val : itemRealQuantity.values()) {
            totalPickQuantity = totalPickQuantity.add(val);
        }

        // 更新主表 待拣货数量（0）、拣货数量、状态
        iPickingService.update(new UpdateWrapper<Picking>().lambda()
                .eq(Picking::getGroupCode,groupCode)
                .eq(Picking::getCode, pickingCode)
                .set(Picking::getNotPickingQuantity, 0)
                .set(Picking::getStatus, PickingStatusEnum.FINISH_PICK.getValue())
                .setSql(" stockout_quantity = plan_quantity - " + totalPickQuantity)
                .set(Picking::getPickingQuantity, totalPickQuantity)
                .set(Picking::getPickingCompletionTime, LocalDateTime.now()));

    }





    /**
     * @param pickingVO:
     * @description 获取明细及实际的拣货数量
     * @author xuyang
     * @date 2020/10/23 9:12
     **/
    public Map<PickingItem, BigDecimal> calcRealPickingQuantity(PickingVO pickingVO) {
        //拣货确认明细信息
        List<PickingItemWithRegisterVO> itemWithRegisterVOS = pickingVO.getPickingItemWithRegisterVOList();
        // 根据状态过滤，获取未保存的数据
        List<PickingRegister> unSaveRegisters = itemWithRegisterVOS.stream()
                .filter(e -> e.getStatus().equals(PickingRegisterStatusEnum.NOT_PICK.getValue()))
                .map(e -> {
                    if (e.getPickingQuantity() == null || BigDecimal.ZERO.compareTo(e.getPickingQuantity()) > 0) {
                        throw new ServiceException("请填写正确的拣货数量");
                    }
                    PickingRegister register = new PickingRegister();
                    BeanUtils.copyProperties(e, register);
                    return register;
                }).collect(Collectors.toList());

        // 数据库中的最新的已拣货数据
        List<PickingRegister> allRegisters = iPickingRegisterService.list(new LambdaQueryWrapper<PickingRegister>()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getPickingCode, pickingVO.getCode())
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.FINISH_PICKING.getValue()));
        // 将新的待保存的数据合并
        allRegisters.addAll(unSaveRegisters);

        //获取所有的拣货明细
        List<PickingItem> pickingItems =
                iPickingItemService.list(new LambdaQueryWrapper<PickingItem>()
                 .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingItem::getPickingCode, pickingVO.getCode())
                );

        // 根据确认单统计各明细实际拣货数量
        Map<String, BigDecimal> registerQuantity = new HashMap<>();
        allRegisters.forEach(e -> {
            registerQuantity.put(e.getPickingItemCode(), registerQuantity.getOrDefault(e.getPickingItemCode(), BigDecimal.ZERO).add(e.getPickingQuantity()));
        });
        // 拼接结果，拣货明细 -- 实际拣货量
        Map<PickingItem, BigDecimal> itemWithRealQuantity = pickingItems.stream().collect(Collectors.toMap(e -> e, e -> registerQuantity.getOrDefault(e.getCode(), BigDecimal.ZERO)));

        return itemWithRealQuantity;
    }











    /**
     * 根据拣货单生成出库单信息
     *
     * @param picking
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    public Outbound pickConfirmationGenOutboundInfo(Picking picking) {
        String groupCode = picking.getGroupCode();
        Outbound outbound = null;
        log.info("拣货完成生成出库单。拣货单：{}", JSONUtil.toJsonStr(picking));
        List<PickingRegister> pickingRegisters = iPickingRegisterService.list(new LambdaQueryWrapper<PickingRegister>()
                .eq(PickingRegister::getGroupCode,groupCode)
                .eq(PickingRegister::getPickingCode,picking.getCode())
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.FINISH_PICKING.getValue()));
        log.info("获取拣货单拣货登记明细：{}", JSONUtil.toJsonStr(pickingRegisters));
        pickingRegisters = pickingRegisters.stream().filter(e -> e.getDistributionQuantity().compareTo(BigDecimal.ZERO) > NUM_ZERO).collect(Collectors.toList());
        // 如果拣货确认单为空，即所有商品都缺货登记了，那就不生成出库单
        if (pickingRegisters.isEmpty()) {
            // 拣货数量全为0，则不生成出库单，订单状态要改为待分配
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getWavePickingCode, picking.getWavePickingCode())
                    .set(Delivery::getStatus, DeliveryStatusEnum.UN_DISTRIBUTION.getValue()));

        } else {
            //生成出库单
            outbound =genOutbound(picking);
            //生成出库单明细，出去单确认明细
            Map<String,Object> itemAndRegister = genOutboundItemAndRegister(pickingRegisters,outbound,picking.getReview());
            if(MapUtil.isEmpty(itemAndRegister)){
                log.error("出库单保存失败，没有出库详细信息");
                throw new ServiceException("出库单保存失败；");
            }
            List<OutboundItem> outboundItems = MapUtil.get(itemAndRegister,"item",List.class);
            if(MapUtil.isEmpty(itemAndRegister)){
                log.error("出库单保存失败，没有出库明细");
                throw new ServiceException("出库单保存失败；");
            }
            List<OutboundRegister> outboundRegisters = MapUtil.get(itemAndRegister,"register",List.class);
            if(MapUtil.isEmpty(itemAndRegister)){
                log.error("出库单保存失败，没有出库登记明细");
                throw new ServiceException("出库单保存失败；");
            }
            // 保存出库单
            outboundService.save(outbound);
            iOutboundItemService.saveBatch(outboundItems);
            iOutboundRegisterService.saveBatch(outboundRegisters);
        }
        return outbound;
    }



    /**
     * 拣货确认更新订单拣货数量
     * @author liuxr
     * @date 2021/5/27 9:06
     * @param picking
     */
    @Transactional(rollbackFor = {Exception.class})
    public void pickConfirmationUpdateDeliveryInfo(Picking picking){
        String wavePickingCode = picking.getWavePickingCode();
        int isReview = picking.getReview();
        //  更新订单分配表拣货数量
        iDeliveryDistributionService.update(new UpdateWrapper<DeliveryDistribution>().lambda()
                .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                .eq(DeliveryDistribution::getWavePickingCode, wavePickingCode)
                .setSql("picking_quantity=plan_quantity-pick_stock_out_num")
                .setSql("not_recheck_quantity = picking_quantity"));
        //更新订单明细表拣货数量和状态
        iDeliveryItemService.updatePickingQuantityStatusByWavePickingCode(wavePickingCode, DeliveryStatusEnum.UN_RECHECK.getValue(),UserUtil.getBranchCode());
        iDeliveryService.updatePickingQuantityByWavePickingCode(wavePickingCode,UserUtil.getBranchCode());
        //生成拣货单不需要复核，直接调用出库确认
        if(isReview==0) {
            //更新订单复核数量为拣货数量，
            //拣货数量与计划数量不一致，订单状态为待复核
            //一致为待出库
            int count = iDeliveryService.updateStatusWithoutRecheck(wavePickingCode);
            log.info("拣货单无需复核，更新订单复核数量为拣货数量，波次号{},更新结果:{}",wavePickingCode,count);
        } else {
            // 出库单需要复核，则订单状态更新为“待复核”
            int count = iDeliveryService.updateStatusWithRecheck(wavePickingCode,UserUtil.getBranchCode());
            log.info("拣货单需复核，更新订单复核数量为拣货数量，波次号{},更新结果:{}",wavePickingCode,count);
        }
    }






    /**
     * 构建出库单主单信息
     * @author liuxr
     * @date 2021/5/27 9:25
     * @param picking
     * @return com.arpa.wms.domain.entity.Outbound
     */
    private Outbound genOutbound(Picking picking){
        String groupCode = picking.getGroupCode();
        // 生成出库单
        Outbound outbound = new Outbound();
        //查询订单主要用来填充出库单的承运商信息
        Delivery delivery = iDeliveryService.getOne(new LambdaQueryWrapper<Delivery>().select(Delivery::getWaybillCode).eq(Delivery::getWavePickingCode, picking.getWavePickingCode())
                .eq(Delivery::getGroupCode, groupCode));
        BeanUtil.copyProperties(picking, outbound);
        // 生成出库单号  CK+货主编码+yyyyMMdd+四位流水号
        outbound.setCode(OrderIdGeneratorUtil.generatorOrderId(OrderIdGeneratorUtil.OrderTypeEnum.CK, picking.getShipmentCode(),groupCode));
        outbound.setPickingCode(picking.getCode());
        outbound.setDeleted(0);
        // 计划发货数量 = 拣货数量
        outbound.setPlanQuantity(picking.getPickingQuantity());
        outbound.setIssue(0);
        if(picking.getReview()==0){
            log.info("拣货单无需复核，出库单直接更改为待出库，复核数量为拣货数量：出库单信息{}",JSONUtil.toJsonStr(outbound));
            //拣货单不需要复核，如果不复核直接创建复核单
            outbound.setStatus(OutboundStatusEnum.NOT_OUT.getValue());
            outbound.setRecheckQuantity(picking.getPickingQuantity());
        }else{
            outbound.setStatus(OutboundStatusEnum.NOT_RECHECK.getValue());
        }
        // 出库单中订单数量
        int deliveryCount;
        if (OrderTagEnum.QUANTITY.getValue().equals(picking.getOrderTag())) {
            deliveryCount = NUM_ONE;
        } else {
            deliveryCount = iDeliveryService.count(new QueryWrapper<Delivery>().lambda().eq(Delivery::getWavePickingCode, picking.getWavePickingCode()).eq(Delivery::getGroupCode,groupCode));
        }
        outbound.setDeliveryCount(deliveryCount);
        if (picking.getStockoutQuantity().compareTo(BigDecimal.ZERO)> NUM_ZERO) {
            BigDecimal goodsTotalNum = iDeliveryService.getGoodsTotalNumByWavePickingCode(picking.getWavePickingCode());
            outbound.setGoodsSortQuantity(goodsTotalNum);
        }
        //出库单中订单计划总数量
        BigDecimal deliveryPlanQuantity = iDeliveryService.getTotalNumByWavePickingCode(picking.getWavePickingCode());
        outbound.setDeliveryPlanQuantity(deliveryPlanQuantity);
        outbound.setWaybillCode(delivery.getWaybillCode());
        return outbound;
    }


    /**
     * 构建出库单明细、待复核的出库确认单
     *
     * @param pickingRegisters
     * @param outbound
     */
    private Map<String,Object> genOutboundItemAndRegister(List<PickingRegister> pickingRegisters, Outbound outbound,int isReview) {
        List<OutboundItem> outboundItems = new ArrayList<>();
        List<OutboundRegister> outboundRegisters = new ArrayList<>();
        pickingRegisters.forEach(pickingRegister->{

            OutboundItem outboundItem = new OutboundItem();
            // 复制属性
            BeanUtil.copyProperties(pickingRegister, outboundItem);
            outboundItem.setOutboundCode(outbound.getCode());
            outboundItem.setPickingRegisterCode(pickingRegister.getCode());
            outboundItem.setPlanQuantity(pickingRegister.getPickingQuantity());
            if(isReview==0) {
                outboundItem.setRecheckQuantity(pickingRegister.getPickingQuantity());
                outboundItem.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
            }else{
                outboundItem.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
            }
            // 出库数量初始值为0
            outboundItem.setOutboundQuantity(BigDecimal.ZERO);
            outboundItem.setDeleted(0);
            outboundItem.setTaskCode(StrUtil.EMPTY);
            outboundItem.setOperatorBy(StrUtil.EMPTY);
            outboundItem.setOperatorName(StrUtil.EMPTY);
            outboundItem.setCreatedBy(StrUtil.EMPTY);
            outboundItem.setCode(IdUtil.fastSimpleUUID());
            outboundItems.add(outboundItem);

            // 生成一条待复核的出库复核单
            OutboundRegister outboundRegister = new OutboundRegister();
            // 复制属性
            BeanUtil.copyProperties(pickingRegister, outboundRegister);
            outboundRegister.setOutboundCode(outbound.getCode());
            outboundRegister.setOutboundItemCode(outboundItem.getCode());
            outboundRegister.setPickingRegisterCode(pickingRegister.getCode());
            outboundRegister.setPlanQuantity(pickingRegister.getPickingQuantity());

            if(isReview==0) {
                // 复核数量初始值为总待拣数量
                outboundRegister.setRecheckQuantity(pickingRegister.getPickingQuantity());
                outboundRegister.setNotRecheckQuantity(BigDecimal.ZERO);
                outboundRegister.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
            }else{
                outboundRegister.setNotRecheckQuantity(pickingRegister.getPickingQuantity());
                outboundRegister.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
            }
            outboundRegister.setDeleted(0);
            outboundRegister.setTaskCode(StrUtil.EMPTY);
            outboundRegister.setOperatorBy(StrUtil.EMPTY);
            outboundRegister.setOperatorName(StrUtil.EMPTY);
            outboundRegister.setCreatedBy(StrUtil.EMPTY);
            outboundRegister.setWavePickingCode(outboundItem.getWavePickingCode());
            outboundRegister.setCode(IdUtil.fastSimpleUUID());
            outboundRegisters.add(outboundRegister);
        });

        Map<String,Object> map = new HashMap<>();
        map.put("item",outboundItems);
        map.put("register",outboundRegisters);
        return map;
    }

    /**
     * 预打包按单拣货确认
     * @author rfwang
     * @date 2021年7月6日
     * @param vo
     */
    public void prepackagedCheck(PickingVO vo){
        String groupCode = UserUtil.getBranchCode();
        // 判断拣货单及状态
        Picking picking = iPickingService.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, vo.getCode()).eq(Picking::getGroupCode,groupCode));
        if (picking == null) {
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // “待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (StrUtil.equals(picking.getStatus(), PickingStatusEnum.FINISH_PICK.getValue())) {
            throw new ServiceException("拣货单已经拣货完成，请确认。");
        }
        //拣货确认明细
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = vo.getPickingItemWithRegisterVOList();
        if (IterUtil.isEmpty(pickingItemWithRegisterVOS)) {
            throw new ServiceException("拣货明细不能为空，请确认");
        }
        // 获取拣货明细及明细对应的实际拣货数量
        Map<PickingItem, BigDecimal> itemWithRealQuantity = calcRealPickingQuantity(vo);
        //检验打包数量
        BigDecimal packQuantity = validatePackageNum(picking,pickingItemWithRegisterVOS,itemWithRealQuantity);
        // 更新明细，如果存在缺货登记的信息， 则需要1. 更新各明细的缺货数量；2.返还分配的数据，并锁定
        //处理缺货
        singlePickinghandleOutStock(vo.getCode(),itemWithRealQuantity,groupCode);
        //按单拣货，更新拣货明细拣货数量、缺货数量及拣货状态拣货完成
        singlePickingUpdateItemPickingQuantityAndStockoutQuantity(picking.getCode(),itemWithRealQuantity,groupCode);

        savePCUnSaveRegister(pickingItemWithRegisterVOS,picking.getCode(),picking.getWavePickingCode(),groupCode);

        // 删除确认单中未拣货的明细数据
        iPickingRegisterService.remove(new QueryWrapper<PickingRegister>().lambda().eq(PickingRegister::getPickingCode, picking.getCode())
                .eq(PickingRegister::getGroupCode,groupCode)
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));

        //  更新预打包分配表拣货数量及状态、 更新分配表明细拣货数量及状态、 更新预打包主单打包数量及状态
        updatePrepackagedInfo(picking.getPrepackagedCode(),packQuantity);

        //处理订单完全缺货
        handleWholeStockOutPrepackaged(picking.getPrepackagedCode());

        //生成预打包库存并更新临时库存
        handleInventory(picking.getPrepackagedCode(),picking.getCode(),picking.getWarehouseCode());

    }

    private BigDecimal validatePackageNum(Picking picking, List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS,Map<PickingItem, BigDecimal> itemWithRealQuantity){
        Set<BigDecimal> packNumSet = new HashSet<>();
        //按商品编码进行分组
        Map<String, List<PickingItemWithRegisterVO>> mapList = pickingItemWithRegisterVOS.stream().collect(Collectors.groupingBy(PickingItemWithRegisterVO::getGoodsCode));
        //1、商品未拣货，判断各商品填写的拣货数量/单件数量是否一致，如果不一致，提示：商品分配数量与实际包裹数不一致，不能生成拣货单；
        BigDecimal pickingQuantity = picking.getPickingQuantity();
        if (pickingQuantity.compareTo(BigDecimal.ZERO) == 0) {
            for (Map.Entry<String, List<PickingItemWithRegisterVO>> entry : mapList.entrySet()) {
                //汇总相同商品的拣货数量
                BigDecimal itemTotalPickingQuantity = entry.getValue().stream().map(PickingItemWithRegisterVO::getPickingQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                //获取单包裹组合数量
                BigDecimal combinationQuantity = entry.getValue().get(NUM_ZERO).getCombinationQuantity();
                BigDecimal packQuantity =  itemTotalPickingQuantity.divide(combinationQuantity,6, BigDecimal.ROUND_HALF_UP);
                if (!packNumSet.contains(packQuantity)) {
                    packNumSet.add(packQuantity);
                }
                if (packNumSet.size() > NumConst.NUM_ONE) {
                    throw new ServiceException("商品分配数量与实际包裹数不一致，不能生成拣货单");
                }
            }
        }
        //包裹数必须为整数  注：不存在包裹数量为0的情况
        if (packNumSet.size() == NUM_ONE ) {
            BigDecimal packQuantity = packNumSet.iterator().next();
            BigDecimal packNum = packQuantity.setScale(0, BigDecimal.ROUND_HALF_DOWN);
            if (packQuantity.compareTo(packNum) != 0) {
                throw new ServiceException("打包数量必须为整数，请重新分配库存");
            }
            return packQuantity;
        }
        //2.商品已拣货，判断已拣货的商品数量是否大于拣货数量/单件数量最小的数值，如果大于，提示是否需要退拣；
        //获取包裹数
        BigDecimal packNumOfFinishPick;
        List<PickingItem> pickingItemList = iPickingItemService.list(new LambdaQueryWrapper<PickingItem>().eq(PickingItem::getGroupCode,UserUtil.getBranchCode()).eq(PickingItem::getPickingCode, picking.getCode())
                .eq(PickingItem::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
        PickingItem item = pickingItemList.get(NUM_ZERO);
        packNumOfFinishPick = item.getPickingQuantity().divide(item.getCombinationQuantity()).setScale(6,BigDecimal.ROUND_HALF_UP);
        if (pickingQuantity.compareTo(BigDecimal.ZERO) > NUM_ZERO && pickingQuantity.compareTo(picking.getPlanQuantity()) < NUM_ZERO) {
            itemWithRealQuantity.forEach((k,v)->{
                // 获取差异数量 = 分配数量 - 缺货登记数量 - （实际+新增拣货数量）
                BigDecimal packNum = v.divide(k.getCombinationQuantity()).setScale(6,BigDecimal.ROUND_HALF_UP);
                if (packNum.compareTo(packNumOfFinishPick) > NUM_ZERO) {
                    throw new ServiceException("当前商品拣货数量超出已拣货商品可组成包裹数");
                }
                if (packNum.compareTo(packNumOfFinishPick) < NUM_ZERO) {
                    throw new ServiceException("当前商品拣货数量小于已拣货商品可组成包裹数，是否自动退拣");
                }
            });
        }
        //包裹数必须为整数
        BigDecimal packInt = packNumOfFinishPick.setScale(0, BigDecimal.ROUND_HALF_DOWN);
        if (packNumOfFinishPick.compareTo(packInt) != 0) {
            throw new ServiceException("打包数量必须为整数，请重新分配库存");
        }
        return packNumOfFinishPick;

    }

     /**
     * 更新预打包模块各表的拣货数量
     *
     * @param prepackaged
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void updatePrepackagedInfo(String prepackaged,BigDecimal packQuantity) {
        // 更新分配单拣货数量及状态
        prepackagedDistributionService.updatePickingQuantity(prepackaged);
        // 更新明细拣货数量及状态
        prepackagedItemService.updatePickingQuantity(prepackaged);
        // 更新预打包单已拣包裹总数及状态
        PrepackagedDTO prepackagedDTO = new PrepackagedDTO();
        prepackagedDTO.setCode(prepackaged);
        prepackagedDTO.setStatus(PrepackagedStatusEnum.PICKED.getValue());
        prepackagedDTO.setPickingQuantity(packQuantity.intValue());
        prepackagedService.updatePackQuantityAndStatus(prepackagedDTO);

    }

    /**
     * 订单的拣货数量为0，说明完全缺货
     *
     * @param prepackagedCode
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleWholeStockOutPrepackaged(String prepackagedCode) {
        Prepackaged prepackaged = prepackagedService.getOne(new QueryWrapper<Prepackaged>().lambda().eq(Prepackaged::getCode, prepackagedCode));
        if (prepackaged.getPickingQuantity() == 0) {
            prepackagedService.update(new UpdateWrapper<Prepackaged>().lambda().eq(Prepackaged::getCode, prepackaged.getCode()).set(Prepackaged::getStatus,PrepackagedStatusEnum.UN_DISTRIBUTION.getValue()).set(Prepackaged::getDistributionQuantity,BigDecimal.ZERO));
            prepackagedItemService.update(new UpdateWrapper<PrepackagedItem>().lambda().eq(PrepackagedItem::getPrepackagedCode, prepackaged.getCode()).set(PrepackagedItem::getDistributionQuantity,BigDecimal.ZERO).set(PrepackagedItem::getStatus,PrepackagedStatusEnum.ADD.getValue()).set(PrepackagedItem::getDistributionQuantity,BigDecimal.ZERO));
            prepackagedDistributionService.remove(new QueryWrapper<PrepackagedDistribution>().lambda().eq(PrepackagedDistribution::getPrepackagedCode, prepackaged.getCode()));
        }
    }

    @Transactional(rollbackFor = {Exception.class})
    public void handleInventory(String prepackagedCode,String pickingCode,String warehouseCode) {
        if (StrUtil.isEmpty(prepackagedCode)) {
            log.info("出库确认更新库存跳过，预打包单为空；");
            return;
        }
        //获取所有已确认的订单分配信息
        List<PrepackagedDistribution> prepackagedDistributions = prepackagedDistributionService.list(new LambdaQueryWrapper<PrepackagedDistribution>()
                .select(PrepackagedDistribution::getCode,
                        PrepackagedDistribution::getPrepackagedCode,
                        PrepackagedDistribution::getInventoryCode,
                        PrepackagedDistribution::getOldInventoryCode,
                        PrepackagedDistribution::getDistributionQuantity,
                        PrepackagedDistribution::getPickingQuantity,
                        PrepackagedDistribution::getPickStockOutNum)
                .eq(PrepackagedDistribution::getPrepackagedCode, prepackagedCode));
        //获取预打包明细集合，按商品编码排序
        List<PrepackagedItem> prepackagedItems = prepackagedItemService.list(new LambdaQueryWrapper<PrepackagedItem>()
                .eq(PrepackagedItem::getPrepackagedCode,prepackagedCode)).stream()
                .sorted(Comparator.comparing(PrepackagedItem::getGoodCode))
                .collect(Collectors.toList());
        //通过分配明细获取所有临时库存标识
        Set<String> tempInventoryCodeSet = prepackagedDistributions.parallelStream().map(PrepackagedDistribution::getInventoryCode).collect(Collectors.toSet());

        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = iInventoryService.list(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode, tempInventoryCodeSet));

        //将库存信息通过库存code转成map
        Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));

        List<InventoryOperationDto> subtract = prepackagedDistributions.parallelStream().map(d -> {
            InventoryOperationDto dto = new InventoryOperationDto();
            dto.setUuid(IdUtil.simpleUUID());
            dto.setQuantity(d.getPickingQuantity());
            dto.setInventoryCode(d.getInventoryCode());
            dto.setInventory(inventoryMap.get(d.getInventoryCode()));
            return dto;
        }).collect(Collectors.toList());

        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        String groupCode = UserUtil.getBranchCode();
        //创建减库存操作记录
        subtract.forEach(r -> {
            //临时库存减库存操作记录
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r.getInventory(), record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getInventoryCode());
            record.setSourceCode(pickingCode);
            record.setNum(r.getQuantity().negate());
            record.setType(InventoryRecordTypeEnum.PICK.getValue());
            inventoryRecords.add(record);
        });

        //生成预打包库存
        List<Inventory> prepackagedInvList = genPrepackagedInventory(inventories,prepackagedItems,warehouseCode);
        List<InventoryRecord> prepackagedInvRecords = new ArrayList<>();
        //创建新增预打包库存操作记录
        prepackagedInvList.forEach(r -> {
            //预打包库存新增操作记录
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r, record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getCode());
            record.setSourceCode(pickingCode);
            record.setNum(r.getQuantity());
            record.setType(InventoryRecordTypeEnum.PICK.getValue());
            prepackagedInvRecords.add(record);
        });

        //批量减少临时库存
        inventoryOperationService.subtractBatch(subtract);
        //批量插入操作记录（库存减少）
        iInventoryRecordService.saveBatch(inventoryRecords);
        //批量保存打包库存
        inventoryService.saveBatch(prepackagedInvList);
        //批量插入操作记录(预打包)
        iInventoryRecordService.saveBatch(prepackagedInvRecords);


    }

    private List<Inventory> genPrepackagedInventory(List<Inventory> inventories,List<PrepackagedItem> prepackagedItems,String warehouseCode){
        //生成快速匹配号
        String fastMatching;
        StringBuilder sb = new StringBuilder();
        prepackagedItems.stream().forEach(e -> {
            sb.append(e.getGoodCode() + "_" + e.getCombinationQuantity() + "#");
        });
        sb.replace(sb.length() - 1, sb.length(), "");
        fastMatching = sb.toString();
        Set<String> goodCodeSet = new HashSet<>();
        List<Inventory> prepackagedInvList = Lists.newArrayList();
        Map<String, PrepackagedItem> mapItem = prepackagedItems.stream().collect(Collectors.toMap(PrepackagedItem::getGoodCode, e -> e));
        //获取临时库位
        Location location = iLocationService.getOne(new LambdaQueryWrapper<Location>()
                .eq(Location::getWarehouseCode, warehouseCode)
                .eq(Location::getSerialNumber, CommonConst.YDKW));
        for (Inventory inventory : inventories) {
            if (!goodCodeSet.contains(inventory.getGoodCode())) {
                goodCodeSet.add(inventory.getGoodCode());
                Inventory inv = new Inventory();
                BeanUtil.copyProperties(inventory,inv);
                inv.setCode(IdUtil.simpleUUID());
                inv.setLot(null);
                inv.setLocation(location.getCode());
                inv.setTempInventory(NUM_ONE);
                inv.setQuantity(mapItem.get(inventory.getGoodCode()).getPickingQuantity());
                inv.setTempCode(prepackagedItems.get(NUM_ZERO).getPrepackagedCode());
                inv.setTempItemCode(mapItem.get(inventory.getGoodCode()).getCode());
                inv.setFastMatching(fastMatching);
                inv.setOldInventoryCode(inventory.getCode());
                inv.setOldLocation(inventory.getLocation());
                prepackagedInvList.add(inv);
            }
        }
        return prepackagedInvList;
    }
    /**
     * 拣货确认分布式锁key
     *
     * @param code 拣货单号
     * @return
     */
    private String getPickingCheckLockKey(String code) {
        return String.format("picking-check-lock-%s", code);
    }

    /**
     * 拣货确认分布式锁key
     *
     * @param code 拣货单号
     * @return
     */
    private String getOutConfirmLockKey(String code) {
        return String.format("outConfirm-lock-%s", code);
    }
}
